package com.gitee.wsl.compose.ui.calendar.yearcalendar

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.unit.Dp
import com.gitee.wsl.compose.ui.calendar.view.DaySlate
import com.gitee.wsl.compose.ui.calendar.view.MonthBodySlate
import com.gitee.wsl.compose.ui.calendar.view.MonthHeaderFooterSlate
import com.gitee.wsl.time.calendar.bean.CalendarMonth
import com.gitee.wsl.time.calendar.bean.CalendarYear
import com.gitee.wsl.time.local.KlockLocale
import kotlinx.datetime.DayOfWeek
import kotlin.math.min

typealias YearHeaderFooterSlate = @Composable ColumnScope.(CalendarYear) -> Unit

typealias YearBodySlate = @Composable ColumnScope.(CalendarYear, content: @Composable () -> Unit) -> Unit

typealias YearContainerSlate = (@Composable LazyItemScope.(CalendarYear, container: @Composable () -> Unit) -> Unit)

typealias YearMonthContainerSlate = @Composable BoxScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit

object YearHeaderFooter

object YearBody

object YearContainer

object YearMonthContainer

val YearBody.default: YearBodySlate get() = defaultYearBody

val YearMonthContainer.default: YearMonthContainerSlate get() = defaultMonthContainer

val YearContainer.default: YearContainerSlate get() = defaultYearContainer

val YearHeaderFooter.default: YearHeaderFooterSlate get() = {}

@Suppress("FunctionName")
internal fun LazyListScope.YearCalendarMonths(
    yearCount: Int,
    yearData: (offset: Int) -> CalendarYear,
    monthColumns: Int,
    monthVerticalSpacing: Dp,
    monthHorizontalSpacing: Dp,
    yearBodyContentPadding: PaddingValues,
    contentHeightMode: YearContentHeightMode,
    isMonthVisible: ((month: CalendarMonth) -> Boolean)?,
    dayContent: DaySlate,
    monthHeader: MonthHeaderFooterSlate?,
    monthBody: MonthBodySlate?,
    monthFooter: MonthHeaderFooterSlate?,
    monthContainer: YearMonthContainerSlate?,
    yearHeader: YearHeaderFooterSlate?,
    yearBody: YearBodySlate?,
    yearFooter: YearHeaderFooterSlate?,
    yearContainer: YearContainerSlate?,
    onItemPlaced: (itemCoordinates: YearItemCoordinates) -> Unit,
    local: KlockLocale = KlockLocale.default,
    firstDayOfWeek: DayOfWeek= KlockLocale.default.firstDayOfWeek,
) {
    val hasYearContainer = yearContainer != null
    val hasMonthContainer = monthContainer != null

    val yearBody = yearBody?: defaultYearBody
    val yearContainer = yearContainer?: defaultYearContainer
    val monthContainer = monthContainer?: defaultMonthContainer
    val monthBody = monthBody?: defaultMonthBody

    items(
        count = yearCount,
        key = { offset -> yearData(offset).year.value }
    ) { yearOffset ->
        val year = yearData(yearOffset)
        val fillHeight = when (contentHeightMode) {
            YearContentHeightMode.Wrap -> false
            YearContentHeightMode.Fill,
            YearContentHeightMode.Stretch,
            -> true
        }
        val months = isMonthVisible.apply(year.months)

        val currentOnItemPlaced by rememberUpdatedState(onItemPlaced)
        val itemCoordinatesStore = remember(year.year) {
            YearItemCoordinatesStore(months.first(), currentOnItemPlaced)
        }
        Box(Modifier.onPlaced(itemCoordinatesStore::onItemRootPlaced)) {
            yearContainer(year) {
                Column(
                    modifier = Modifier
                        .then(
                            if (hasYearContainer) {
                                Modifier.fillMaxWidth()
                            } else {
                                Modifier.fillParentMaxWidth()
                            },
                        )
                        .then(
                            if (fillHeight) {
                                if (hasYearContainer) {
                                    Modifier.fillMaxHeight()
                                } else {
                                    Modifier.fillParentMaxHeight()
                                }
                            } else {
                                Modifier.wrapContentHeight()
                            },
                        ),
                ) {
                    yearHeader?.invoke(this, year)
                    yearBody(year) {
                        CalendarGrid(
                            modifier = Modifier
                                .fillMaxWidth()
                                .then(if (fillHeight) Modifier.weight(1f) else Modifier.wrapContentHeight())
                                .padding(yearBodyContentPadding),
                            monthColumns = monthColumns,
                            monthCount = months.count(),
                            fillHeight = fillHeight,
                            monthVerticalSpacing = monthVerticalSpacing,
                            monthHorizontalSpacing = monthHorizontalSpacing,
                            onFirstMonthPlaced = itemCoordinatesStore::onFirstMonthPlaced,
                        ) { monthOffset ->
                            val month = months[monthOffset]
                            monthContainer(month) {
                                Column(
                                    modifier = Modifier
                                        .then(if (hasMonthContainer) Modifier.fillMaxWidth() else Modifier)
                                        .then(
                                            if (fillHeight) {
                                                if (hasMonthContainer) Modifier.fillMaxHeight() else Modifier
                                            } else {
                                                Modifier.wrapContentHeight()
                                            },
                                        ),
                                ) {
                                    monthHeader?.invoke(this, month,firstDayOfWeek,local)
                                    monthBody(month) {
                                        Column(
                                            modifier = Modifier
                                                .fillMaxWidth()
                                                .then(
                                                    if (fillHeight) {
                                                        Modifier.weight(1f)
                                                    } else {
                                                        Modifier.wrapContentHeight()
                                                    },
                                                ),
                                        ) {
                                            for ((row, week) in month.weekDays.withIndex()) {
                                                Row(
                                                    modifier = Modifier
                                                        .fillMaxWidth()
                                                        .then(
                                                            if (contentHeightMode == YearContentHeightMode.Stretch) {
                                                                Modifier.weight(1f)
                                                            } else {
                                                                Modifier.wrapContentHeight()
                                                            },
                                                        ),
                                                ) {
                                                   week.days.forEachIndexed { column, day->
                                                       Box(
                                                           modifier = Modifier
                                                               .weight(1f)
                                                               .clipToBounds()
                                                               .onFirstDayPlaced(
                                                                   monthIndex = monthOffset,
                                                                   dateRow = row,
                                                                   dateColumn = column,
                                                                   onFirstDayPlaced = itemCoordinatesStore::onFirstDayPlaced,
                                                               ),
                                                       ) {
                                                           dayContent(day)
                                                       }
                                                   }
                                                }
                                            }
                                        }
                                    }
                                    monthFooter?.invoke(this, month,firstDayOfWeek,local)
                                }
                            }
                        }
                    }
                    yearFooter?.invoke(this, year)
                }
            }
        }
    }
}

@Composable
private inline fun CalendarGrid(
    monthColumns: Int,
    fillHeight: Boolean,
    monthVerticalSpacing: Dp,
    monthHorizontalSpacing: Dp,
    monthCount: Int,
    modifier: Modifier = Modifier,
    noinline onFirstMonthPlaced: (coordinates: LayoutCoordinates) -> Unit,
    crossinline content: @Composable BoxScope.(Int) -> Unit,
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(monthVerticalSpacing)
    ) {
        val rows = (monthCount / monthColumns) + min(monthCount % monthColumns, 1)
        for (rowId in 0 until rows) {
            val firstIndex = rowId * monthColumns
            Row(
                modifier = Modifier.then(
                    if (fillHeight) Modifier.weight(1f) else Modifier,
                ),
                horizontalArrangement = Arrangement.spacedBy(monthHorizontalSpacing),
            ) {
                for (columnId in 0 until monthColumns) {
                    val index = firstIndex + columnId
                    Box(
                        modifier = Modifier
                            .weight(1f)
                            .onFirstMonthPlaced(index, onFirstMonthPlaced),
                    ) {
                        if (index < monthCount) {
                            content(index)
                        }
                    }
                }
            }
        }
    }
}

@Stable
internal class YearItemCoordinatesStore(
    private val firstMonth: CalendarMonth,
    private val onItemPlaced: (itemCoordinates: YearItemCoordinates) -> Unit,
) {
    private var itemRootCoordinates: LayoutCoordinates? = null
    private var firstDayCoordinates: LayoutCoordinates? = null
    private var firstMonthCoordinates: LayoutCoordinates? = null

    fun onItemRootPlaced(coordinates: LayoutCoordinates) {
        itemRootCoordinates = coordinates
        check()
    }

    fun onFirstMonthPlaced(coordinates: LayoutCoordinates) {
        firstMonthCoordinates = coordinates
        check()
    }

    fun onFirstDayPlaced(coordinates: LayoutCoordinates) {
        firstDayCoordinates = coordinates
        check()
    }

    private fun check() {
        val itemRootCoordinates = itemRootCoordinates ?: return
        val firstMonthCoordinates = firstMonthCoordinates ?: return
        val firstDayCoordinates = firstDayCoordinates ?: return
        val itemCoordinates = YearItemCoordinates(
            firstMonth = firstMonth,
            itemRootCoordinates = itemRootCoordinates,
            firstMonthCoordinates = firstMonthCoordinates,
            firstDayCoordinates = firstDayCoordinates,
        )
        onItemPlaced(itemCoordinates)
    }
}

private fun Modifier.onFirstDayPlaced(
    monthIndex: Int,
    dateRow: Int,
    dateColumn: Int,
    onFirstDayPlaced: (coordinates: LayoutCoordinates) -> Unit,
) = if (monthIndex == 0 && dateRow == 0 && dateColumn == 0) {
    onPlaced(onFirstDayPlaced)
} else {
    this
}

private  fun Modifier.onFirstMonthPlaced(
    monthIndex: Int,
    onFirstMonthPlaced: (coordinates: LayoutCoordinates) -> Unit,
) = if (monthIndex == 0) {
    onPlaced(onFirstMonthPlaced)
} else {
    this
}

internal  fun ((month: CalendarMonth) -> Boolean)?.apply(months: List<CalendarMonth>) = if (this != null) {
    months.filter(this).also {
        check(it.isNotEmpty()) {
            "Cannot remove all the months in a year, " +
                "use the startYear and endYear parameters to remove full years."
        }
    }
} else {
    months
}

internal fun rowColumn(monthIndex: Int, monthColumns: Int): Pair<Int, Int> {
    val row = monthIndex / monthColumns
    val column = monthIndex % monthColumns
    // val index = row * monthColumns + column
    return row to column
}

private val defaultYearContainer: (@Composable LazyItemScope.(CalendarYear, container: @Composable () -> Unit) -> Unit) =
    { _, container -> container() }

private val defaultYearBody: (@Composable ColumnScope.(CalendarYear, content: @Composable () -> Unit) -> Unit) =
    { _, content -> content() }

private val defaultMonthContainer: (@Composable BoxScope.(CalendarMonth, container: @Composable () -> Unit) -> Unit) =
    { _, container -> container() }

private val defaultMonthBody: (@Composable ColumnScope.(CalendarMonth, content: @Composable () -> Unit) -> Unit) =
    { _, content -> content() }
